这两天在做 spring cloud 的 API gateway 的时候,遇到了一个全局错误处理的坑,我在在 spring security 中加入了一个 filter,该 filter 用来验证 token 是否合法,如果该 token 不合法,就抛出自定义错误 InvalidTokenException ,并且要返回的状态码为403,告诉前端该用户未认证。
我开始以为使用 @ControllerAdvice
来定义全局错误处理即可,最后发现过滤器中抛出了错误 @ControllerAdvice
却无法捕获到,并抛出我需要的错误信息。最后阅读 spring 的官方文档发现,spring 的全局错误处理不是只有 @ControllerAdvice
。
@ControllerAdvice
主要处理的就是 controller
层的错误信息,而没有进入 controller
层的错误 @ControllerAdvice
是无法处理的,那么我需要另外的一个全局错误处理。
@ControllerAdvice
public class ExceptionTranslator {
@ExceptionHandler(ConcurrencyFailureException.class)
@ResponseStatus(HttpStatus.CONFLICT)
@ResponseBody
public ErrorVM processConcurencyError(ConcurrencyFailureException ex) {
return new ErrorVM(ErrorConstants.ERR_CONCURRENCY_FAILURE);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ErrorVM processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();
return processFieldErrors(fieldErrors);
}
@ExceptionHandler(CustomParameterizedException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ParameterizedErrorVM processParameterizedValidationError(CustomParameterizedException ex) {
return ex.getErrorVM();
}
@ExceptionHandler(InvalidTokenException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
@ResponseBody
public ErrorVM processInvalidTokenException(InvalidTokenException ex) {
return new ErrorVM(ErrorConstants.INVALID_TOKEN, ex.getMessage());
}
@ExceptionHandler(AccessDeniedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
@ResponseBody
public ErrorVM processAccessDeniedException(AccessDeniedException e) {
return new ErrorVM(ErrorConstants.ERR_ACCESS_DENIED, e.getMessage());
}
private ErrorVM processFieldErrors(List<FieldError> fieldErrors) {
ErrorVM dto = Objects.nonNull(fieldErrors.get(0)) ? new ErrorVM(ErrorConstants.ERR_VALIDATION, fieldErrors.get(0).getDefaultMessage()) : new ErrorVM(ErrorConstants.ERR_VALIDATION);
for (FieldError fieldError : fieldErrors) {
dto.add(fieldError.getObjectName(), fieldError.getField(), fieldError.getCode());
}
return dto;
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
@ResponseBody
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
public ErrorVM processMethodNotSupportedException(HttpRequestMethodNotSupportedException exception) {
return new ErrorVM(ErrorConstants.ERR_METHOD_NOT_SUPPORTED, exception.getMessage());
}
@ExceptionHandler(CustomException.class)
@ResponseBody
@ResponseStatus(HttpStatus.IM_USED)
public ErrorVM processCustomException(CustomException ex) {
return new ErrorVM(ErrorConstants.ERR_CUSTOM, ex.getMessage());
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorVM> processRuntimeException(Exception ex) {
BodyBuilder builder;
ErrorVM errorVM;
ResponseStatus responseStatus = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);
if (responseStatus != null) {
builder = ResponseEntity.status(responseStatus.value());
errorVM = new ErrorVM("error." + responseStatus.value().value(), responseStatus.reason());
} else {
builder = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR);
errorVM = new ErrorVM(ErrorConstants.ERR_INTERNAL_SERVER_ERROR, "Internal server error");
}
return builder.body(errorVM);
}
}
BasicErrorController
这个类就是用来捕获 /error
的所有错误,而过滤器中的错误会被重定向到 /error
。我编写一个新的控制层类 TokenErrorController
并继承 BasicErrorController
类,这样错误 json 类型的错误都会被重定向到这个控制层里。
@RestController
public class TokenErrorController extends BasicErrorController {
public TokenErrorController(){
super(new DefaultErrorAttributes(), new ErrorProperties());
}
private static final String PATH = "/error";
@RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
if (!Strings.isNullOrEmpty((String)body.get("exception")) && body.get("exception").equals(InvalidTokenException.class.getName())){
body.put("status", HttpStatus.FORBIDDEN.value());
status = HttpStatus.FORBIDDEN;
}
return new ResponseEntity<Map<String, Object>>(body, status);
}
@Override
public String getErrorPath() {
return PATH;
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。